# CMakeLists.txt to build the NBodylib library
#
# ICRAR - International Centre for Radio Astronomy Research
# (c) UWA - The University of Western Australia, 2018
# Copyright by UWA (in the framework of the ICRAR)
# All rights reserved
#
# Contributed by Rodrigo Tobar
#
# This file is part of VELOCIraptor.

cmake_minimum_required(VERSION 3.0)

set(_subdirs Analysis Cosmology InitCond KDTree Math NBody)

# We have the version here
file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/VERSION NBODY_VERSION)
string(REPLACE "." ";" VERSION_LIST ${NBODY_VERSION})
list(GET VERSION_LIST 0 NBODY_VERSION_MAJOR)
list(GET VERSION_LIST 1 NBODY_VERSION_MINOR)

# We have only C++ sources, but until cmake 3.4 the check_symbol_exists
# macro didn't work properly if only defining C++ as the project language
if( ${CMAKE_MAJOR_VERSION} EQUAL 3 AND ${CMAKE_MINOR_VERSION} LESS 4 )
	set(NBODY_LANGS C CXX)
else()
	set(NBODY_LANGS CXX)
endif()

project(NBodyLib VERSION ${NBODY_VERSION} LANGUAGES ${NBODY_LANGS})
set(CMAKE_CXX_STANDARD 11)


# Function to add a "doc" target, which will doxygen out the given
# list of directories
function(try_add_doc_target doc_dirs)
	find_program(DOXYGEN_FOUND doxygen)
	if (NOT DOXYGEN_FOUND)
		return()
	endif()

	# Create a target for each individual doc directory, then a final one
	# that depends on them all
	message("-- Adding doc target for directories: ${doc_dirs}")
	set(_dependencies "")
	set(x 1)
	foreach(_doc_dir IN ITEMS ${doc_dirs})
		add_custom_command(OUTPUT ${_doc_dir}/xml/index.xml
		                   COMMAND doxygen
		                   WORKING_DIRECTORY ${_doc_dir})
		add_custom_target(doc_${x}
		                  COMMAND doxygen
		                  WORKING_DIRECTORY ${_doc_dir})
		list(APPEND _dependencies doc_${x})
		math(EXPR x "${x} + 1")
	endforeach()
	add_custom_target(doc DEPENDS "${_dependencies}")
endfunction()

# Are we part of an outside build?
# If so, we want to let the caller know what to include, define, etc at the end
set(_export OFF)
get_directory_property(_hasParent PARENT_DIRECTORY)
if (_hasParent)
	set(_export ON)
endif()

# Options users can give on the command line via -D
macro(nbody_option optname optdesc status )
	if (NOT DEFINED NBODY_${optname})
		option(NBODY_${optname} "${optdesc}" "${status}")
	endif()
endmacro()

# Optional dependencies
nbody_option(OPENMP "Attempt to include OpenMP support in NBodyLib" ON)

# Precision options
nbody_option(SINGLE_PRECISION  "Use single point precision to store all properties and perform all calculations" OFF)
nbody_option(LONG_INT          "Use long ints to represent all integers. Needed if dealing with more than MAXINT number of particles" OFF)

# Particle class details options
nbody_option(NO_MASS                    "Do not store the mass as all particles are the same mass" OFF)
nbody_option(SINGLE_PARTICLE_PRECISION  "Do not store the mass as all particles are the same mass" OFF)
nbody_option(UNSIGNED_PARTICLE_PIDS     "Use unsigned particle PIDs" OFF)
nbody_option(UNSIGNED_PARTICLE_IDS      "Use unsigned particle IDs" OFF)
nbody_option(USE_GAS                    "Particle class has gas" OFF)
nbody_option(USE_STARS                  "Particle class has stars" OFF)
nbody_option(USE_BARYONS                "Gas and Stars" OFF)
nbody_option(USE_HYDRO                  "All particle types (gas, stars, black holes, etc)" OFF)
nbody_option(USE_EXTRA_DM_PROPERTIES    "DM particle have extra properties" OFF)
nbody_option(USE_SWIFT_INTERFACE		"SWIFT Interface" OFF)
nbody_option(USE_EXTRA_INPUT_INFO		"Extra Input Info" OFF)
nbody_option(USE_EXTRA_FOF_INFO		    "Extra FOF Info" OFF)
nbody_option(USE_LARGE_KDTREE		    "Require large mem KDTree as there are more than > max 32-bit integer entries" OFF)

# Process high-level options
if (NBODY_USE_BARYONS)
	set(NBODY_USE_GAS ON)
	set(NBODY_USE_STARS ON)
endif()
if (NBODY_USE_HYDRO)
	set(NBODY_USE_GAS ON)
	set(NBODY_USE_STARS ON)
	set(NBODY_USE_BH ON)
endif()


# Flags used for compiling nbodylib.
# They are optionally exported at the end if there is an outer scope
set(NBODY_INCLUDE_DIRS "")
set(NBODY_DEFINES "")
set(NBODY_LIBS "")
set(NBODY_CXX_FLAGS "")
set(NBODY_LINK_FLAGS "")
set(NBODY_DOC_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/doc)

# Define macros depending on the user's choices
macro(nbody_option_defines varname define)
	if (NBODY_${varname})
		list(APPEND NBODY_DEFINES ${define})
		set(NBODY_HAS_${varname} Yes)
	else()
		set(NBODY_HAS_${varname} No)
	endif()
endmacro()

nbody_option_defines(SINGLE_PRECISION          SINGLEPRECISION)
nbody_option_defines(LONG_INT                  LONGINT)
nbody_option_defines(NO_MASS                   NOMASS)
nbody_option_defines(SINGLE_PARTICLE_PRECISION LOWPRECISIONPOS)
nbody_option_defines(UNSIGNED_PARTICLE_PIDS    PARTICLEUPIDS)
nbody_option_defines(UNSIGNED_PARTICLE_IDS     PARTICLEUIDS)
nbody_option_defines(LARGEKDTREE               LARGETREE)
nbody_option_defines(USE_GAS                   GASON)
nbody_option_defines(USE_STARS                 STARON)
nbody_option_defines(USE_BH                    BHON)
nbody_option_defines(USE_EXTRA_DM_PROPERTIES   EXTRADMON)
nbody_option_defines(USE_SWIFT_INTERFACE       SWIFTINTERFACE)
nbody_option_defines(USE_EXTRA_INPUT_INFO      EXTRAINPUTINFO)
nbody_option_defines(USE_EXTRA_FOF_INFO        EXTRAFOFINFO)
nbody_option_defines(USE_LARGE_KDTREE          LARGETREE)

#
# How we find GSL and set it up
#
macro(find_gsl)
	find_package(GSL REQUIRED)
	if( ${GSL_VERSION} GREATER 2.2 OR ${GSL_VERSION} EQUAL 2.2)
		list(APPEND NBODY_DEFINES HAVE_GSL22)
	endif()
	list(APPEND NBODY_INCLUDE_DIRS ${GSL_INCLUDE_DIRS})
	list(APPEND NBODY_LIBS ${GSL_LIBRARIES})
endmacro()

# How we find OpenMP and set it up
#
macro(find_openmp)
	find_package(OpenMP COMPONENTS CXX)
	if (OPENMP_FOUND)
		list(APPEND NBODY_CXX_FLAGS ${OpenMP_CXX_FLAGS})
		list(APPEND NBODY_LINK_FLAGS ${OpenMP_CXX_FLAGS})
		list(APPEND NBODY_DEFINES USEOPENMP USEOMP)
		set(NBODY_HAS_OPENMP Yes)
	endif()
endmacro()

#
# Now go and find our dependencies, depending on whether
# we actually need them or not
#
find_gsl()

set(NBODY_HAS_OPENMP No)
if (NBODY_OPENMP)
	find_openmp()
endif()


# Add subdirectories to list of include directories
foreach(_subdir IN ITEMS ${_subdirs})
	list(APPEND NBODY_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/src/${_subdir}")
endforeach()

#
# Tell the world what what we are doing
#
macro(nbody_report feature)

	# Output feature name and underscore it in the next line
	message("\n${feature}")
	string(REGEX REPLACE "." "-" _underscores ${feature})
	message("${_underscores}\n")

	set(_args "${ARGN}")
	list(LENGTH _args _nargs)
	math(EXPR _nargs "${_nargs} - 1")
	foreach(_idx RANGE 0 ${_nargs} 2)

		# Items in the list come with a message first, then the variable name
		list(GET _args ${_idx} _msg)
		math(EXPR _idx2 "${_idx} + 1")
		list(GET _args ${_idx2} _varname)

		# We try to keep things up to 80 cols
		string(LENGTH ${_msg} _len)
		math(EXPR _nspaces "75 - ${_len}")
		string(RANDOM LENGTH ${_nspaces} _spaces)
		string(REGEX REPLACE "." " " _spaces "${_spaces}")
		string(CONCAT _msg "${_msg}" ${_spaces})
		message(" ${_msg} ${NBODY_HAS_${_varname}}")
	endforeach()
endmacro()

message("\nNBodyLib successfully configured with the following settings:")
nbody_report(Dependencies "OpenMP" OPENMP)
nbody_report(Types "All calculations/properties stored as float" SINGLE_PRECISION "All integeres are long int" LONG_INT)
nbody_report("Particle data" "Do not store mass, all particles are the same mass" NO_MASS
             "Use single precision to store positions, velocities, other props" SINGLE_PARTICLE_PRECISION
             "Use unsigned particle PIDs" UNSIGNED_PARTICLE_PIDS "Use unsigned particle IDs" UNSIGNED_PARTICLE_IDS
             "Activate gas" USE_GAS "Activate stars" USE_STARS "Activate black holes/sinks" USE_BH 
             "Activate extra dm properties" USE_EXTRA_DM_PROPERTIES 
             "Extra input info stored" USE_EXTRA_INPUT_INFO
             "Extra FOF info stored" USE_EXTRA_FOF_INFO 
             "Large memory KDTree" USE_LARGE_KDTREE
             "Particle compiled for SWIFT" USE_SWIFT_INTERFACE
		 )
message("")
message("Compilation")
message("-----------")
message("")
message(" Include directories: ${NBODY_INCLUDE_DIRS}")
message(" Macros defined: ${NBODY_DEFINES}")
message(" Libs: ${NBODY_LIBS}")
message(" C++ flags: ${NBODY_CXX_FLAGS}")
message(" Link flags: ${NBODY_LINK_FLAGS}")
message("")

#
# Macro used by subdirectories to build each a static library
#
macro(add_nbody_lib libname sources)
	add_library(${libname} OBJECT ${sources})
	target_compile_definitions(${libname} PRIVATE ${NBODY_DEFINES})
	target_include_directories(${libname} PRIVATE ${NBODY_INCLUDE_DIRS})
	if (NBODY_CXX_FLAGS)
		set_target_properties(${libname} PROPERTIES COMPILE_FLAGS ${NBODY_CXX_FLAGS})
	endif()
endmacro()

# Include all subdirectories,
# where the magic happens
foreach(_subdir IN ITEMS ${_subdirs})
	add_subdirectory(src/${_subdir})
endforeach()

# The nbodylib OBJECT library, which can then be used in the same fashion
# to bring all objects together into a final executable/library
add_library(nbodylib_iface INTERFACE)
target_sources(nbodylib_iface INTERFACE
    $<TARGET_OBJECTS:Analysis> $<TARGET_OBJECTS:Cosmology>
    $<TARGET_OBJECTS:InitCond> $<TARGET_OBJECTS:KDTree>
    $<TARGET_OBJECTS:Math> $<TARGET_OBJECTS:NBody>
)

# The final static libnbodylib.a library, in case it's needed on its own
add_library(nbodylib STATIC)
target_link_libraries(nbodylib nbodylib_iface)

# Export the include directories and definitions, if necessary
# If building on our own, add the "doc" target
if (_export)
	set(NBODYLIB_VERSION "${NBODY_VERSION_MAJOR}.${NBODY_VERSION_MINOR}" PARENT_SCOPE)
	set(NBODYLIB_VERSION_MAJOR "${NBODY_VERSION_MAJOR}" PARENT_SCOPE)
	set(NBODYLIB_VERSION_MINOR "${NBODY_VERSION_MINOR}" PARENT_SCOPE)
	set(NBODYLIB_INCLUDE_DIRS "${NBODY_INCLUDE_DIRS}" PARENT_SCOPE)
	set(NBODYLIB_DEFINES "${NBODY_DEFINES}" PARENT_SCOPE)
	set(NBODYLIB_CXX_FLAGS "${NBODY_CXX_FLAGS}" PARENT_SCOPE)
	set(NBODYLIB_LINK_FLAGS "${NBODY_LINK_FLAGS}" PARENT_SCOPE)
	set(NBODYLIB_LIBS "${NBODY_LIBS}" PARENT_SCOPE)
	set(NBODYLIB_DOC_DIRS "${NBODY_DOC_DIRS}" PARENT_SCOPE)
else()
	try_add_doc_target("${NBODY_DOC_DIRS}")
endif()
